REST APIはほぼすべてのWebサービスの基盤です。しかし、「動く」APIと「良い」APIには大きな違いがあります。本記事では、保守性が高く、使いやすいREST APIを設計するための実践的なベストプラクティスを解説します。
1. RESTの基本原則
- リソース指向:URLはリソース(名詞)を表し、操作(動詞)はHTTPメソッドで表現
- ステートレス:各リクエストは独立しており、サーバーにセッション状態を持たない
- 統一インターフェース:一貫した規約に従い、予測可能なAPIを提供
- HATEOAS:レスポンスに次に取れるアクションのリンクを含める
2. エンドポイント命名規則
# ✅ 良い例(リソース名は複数形の名詞)
GET /api/v1/users # ユーザー一覧
GET /api/v1/users/123 # ユーザー詳細
POST /api/v1/users # ユーザー作成
PUT /api/v1/users/123 # ユーザー更新(全体)
PATCH /api/v1/users/123 # ユーザー更新(部分)
DELETE /api/v1/users/123 # ユーザー削除
# ✅ ネストされたリソース
GET /api/v1/users/123/posts # ユーザーの投稿一覧
GET /api/v1/users/123/posts/456 # 特定の投稿
# ❌ 悪い例(動詞を使っている)
GET /api/v1/getUsers
POST /api/v1/createUser
DELETE /api/v1/deleteUser/123
# ❌ 悪い例(camelCase、単数形)
GET /api/v1/userList
GET /api/v1/user/123
✅ 命名のルール
URLにはkebab-case(ハイフン区切り)を使い、クエリパラメータにはcamelCaseを使うのが一般的です。例:/api/v1/blog-posts?sortBy=createdAt
3. HTTPメソッドの正しい使い方
// Express.jsでの実装例
const express = require('express');
const router = express.Router();
// GET - リソースの取得(冪等・安全)
router.get('/users', async (req, res) => {
const { page = 1, limit = 20, sortBy = 'createdAt' } = req.query;
const users = await User.find()
.sort(sortBy)
.skip((page - 1) * limit)
.limit(limit);
res.json({
data: users,
pagination: { page, limit, total: await User.countDocuments() }
});
});
// POST - リソースの作成(冪等ではない)
router.post('/users', async (req, res) => {
const user = await User.create(req.body);
res.status(201).json({ data: user });
});
// PUT - リソースの全体更新(冪等)
router.put('/users/:id', async (req, res) => {
const user = await User.findByIdAndUpdate(req.params.id, req.body, {
new: true, runValidators: true
});
if (!user) return res.status(404).json({ error: 'User not found' });
res.json({ data: user });
});
// DELETE - リソースの削除(冪等)
router.delete('/users/:id', async (req, res) => {
const user = await User.findByIdAndDelete(req.params.id);
if (!user) return res.status(404).json({ error: 'User not found' });
res.status(204).send();
});
広告
4. ステータスコードの使い分け
- 200 OK — リクエスト成功(GET、PUT、PATCH)
- 201 Created — リソース作成成功(POST)
- 204 No Content — 成功だがレスポンスボディなし(DELETE)
- 400 Bad Request — バリデーションエラー
- 401 Unauthorized — 認証が必要
- 403 Forbidden — 権限不足
- 404 Not Found — リソースが見つからない
- 409 Conflict — リソースの競合(重複Eメール等)
- 422 Unprocessable Entity — バリデーションエラー(詳細)
- 429 Too Many Requests — レート制限
- 500 Internal Server Error — サーバーエラー
統一的なエラーレスポンス
{
"error": {
"code": "VALIDATION_ERROR",
"message": "入力データにエラーがあります",
"details": [
{ "field": "email", "message": "有効なメールアドレスを入力してください" },
{ "field": "password", "message": "パスワードは8文字以上必要です" }
]
}
}
5. 認証とセキュリティ
// JWT認証ミドルウェア
const jwt = require('jsonwebtoken');
const authenticate = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({ error: { code: 'UNAUTHORIZED' }});
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
return res.status(401).json({ error: { code: 'INVALID_TOKEN' }});
}
};
// レート制限
const rateLimit = require('express-rate-limit');
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分間
max: 100, // 最大100リクエスト
standardHeaders: true,
legacyHeaders: false,
});
6. バージョニング戦略
- URLパスバージョニング(推奨):
/api/v1/users→/api/v2/users - ヘッダーバージョニング:
Accept: application/vnd.myapi.v2+json - クエリパラメータ:
/api/users?version=2
良いAPI設計は、チームの生産性とシステムの保守性を大きく左右します。この記事のベストプラクティスを参考に、使いやすく拡張性の高いAPIを目指してください。